package lumis.content.wizard;

import lumis.content.wizard.ICreateParams.IField;
import lumis.doui.source.field.type.ISourceFieldDataType;
import lumis.doui.source.field.type.SourceFieldTypes;
import lumis.portal.*;
import lumis.portal.acl.PortalPermissions;
import lumis.portal.authentication.SessionConfig;
import lumis.portal.dao.jdbc.ITransactionJdbc;
import lumis.portal.manager.ManagerException;
import lumis.portal.manager.ManagerFactory;
import lumis.portal.service.ServiceConfig;
import lumis.util.FileUtil;
import lumis.util.ITransaction;
import lumis.util.PortalUtil;
import lumis.util.TextUtil;
import lumis.util.query.*;

import java.io.*;
import java.net.URL;
import java.sql.Connection;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;


/**
 * Implements the content wizard back-end processing.
 * 
 * @since 4.0.0
 * @version $Revision: 10214 $ $Date: 2009-03-17 15:59:27 -0300 (Tue, 17 Mar 2009) $
 */
public class ContentWizardManager implements IContentWizardManager
{
	private IContentWizardServiceDao contentWizardServiceDao = new ContentWizardServiceDaoJdbc();
	
	public ContentWizardServiceConfig get(SessionConfig sessionConfig, String id, ITransaction transaction) throws ManagerException, PortalException
	{
		return contentWizardServiceDao.get(id, transaction);
	}
	
	public ContentWizardServiceConfig findByServiceId(SessionConfig sessionConfig, String serviceId, ITransaction transaction) throws ManagerException, PortalException
	{
		return contentWizardServiceDao.findByServiceId(serviceId, transaction);
	}

	public boolean delete(SessionConfig sessionConfig, String id, ITransaction transaction) throws ManagerException, PortalException
	{
		if (!ManagerFactory.getPortalAclManager().checkPermission(sessionConfig, PortalPermissions.MANAGE_PORTAL, transaction))
			throw new AccessDeniedException();
	
		ContentWizardServiceConfig wizardServiceConfig = get(sessionConfig, id, transaction);

		// verify there is an instance of the service
		Collection<String> serviceInstanceIds = ManagerFactory.getServiceInstanceManager().getIdsByServiceId(sessionConfig, wizardServiceConfig.getServiceId(), transaction);
		if (!serviceInstanceIds.isEmpty())
			throw new ContentWizardException("STR_SERVICE_IS_INSTANTIATED");
		
		// get the service definition
		ServiceConfig serviceConfig = ManagerFactory.getServiceManager().get(sessionConfig, wizardServiceConfig.getServiceId(), transaction);
		//Node serviceDefinitionNode = DouiManagerFactory.getDouiManager().getDouiServiceDefinitionNode(wizardServiceConfig.getServiceId(), transaction);
		
		// unregister service
		contentWizardServiceDao.delete(id, transaction);
		ManagerFactory.getServiceManager().unregister(sessionConfig, wizardServiceConfig.getServiceId(), transaction);
	
		// delete service files
		return FileUtil.deleteDir(PortalContext.getDefinitionPath(serviceConfig.getBaseUrl()));
	}

	public void create(SessionConfig sessionConfig, ICreateParams createParams, ITransaction transaction) throws PortalException
	{
		if (!ManagerFactory.getPortalAclManager().checkPermission(sessionConfig, PortalPermissions.MANAGE_PORTAL, transaction))
			throw new AccessDeniedException();
		
		validateCreateParams(createParams);
		
		File serviceFolder = new File(PortalContext.getDefinitionPath(createParams.getInstallPath()));
		
		// create service folder
		if (serviceFolder.exists())
			throw new ContentWizardException("STR_INSTALL_PATH_ALREADY_EXISTS");
		if (!serviceFolder.mkdirs())
			throw new ContentWizardException("STR_COULD_NOT_CREATE_INSTALL_PATH_FOLDER");
		try
		{
			try
			{
				// create files
				createServiceDefinition(serviceFolder, createParams);
				createDouiDefinition(serviceFolder, createParams);
				createStrings(serviceFolder, createParams);
				String sql = createTableScript(serviceFolder, createParams, transaction);
				
				// execute generated sql, if create option was chosen
				if (createParams.getTable().isCreate())
				{
					ITransactionJdbc daoTransactionJdbc = (ITransactionJdbc) transaction;
					Connection connection = daoTransactionJdbc.getConnection();						
					Statement statement = connection.createStatement();
					try
					{
						statement.execute(sql);
					}
					catch (SQLException e)
					{
						throw new ContentWizardException("STR_ERROR_WHILE_CREATING_TABLE", e);
					}
				}

				// register service
				ManagerFactory.getServiceManager().register(sessionConfig, createParams.getInstallPath(), transaction);
			
				// insert entry in ContentWizardService
				ContentWizardServiceConfig contentWizardServiceConfig = new ContentWizardServiceConfig();
				contentWizardServiceConfig.setId(PortalUtil.generateNewGuid());
				contentWizardServiceConfig.setServiceId(createParams.getName());
				contentWizardServiceDao.add(contentWizardServiceConfig, transaction);
			}
			catch(Exception e)
			{
				// delete folder created
				FileUtil.deleteDir(serviceFolder);
				throw e;				
			}
		}
		catch (PortalException e)
		{
			throw e;
		}		
		catch (Exception e)
		{
			throw new UnexpectedException(e);
		}		
	}

	/**
	 * Validate the content in the given createParams.
	 * @param createParams the createParams to be validated.
	 * @throws PortalException if the createParams are not valid.
	 * @since 4.0.3
	 */
	private void validateCreateParams(ICreateParams createParams) throws PortalException
	{
		// validate createParams
		List<IField> fields = createParams.getTable().getFields();
		boolean hasPrimaryKey = false;
		for (IField field: fields)
		{
			if (field.isPrimaryKey())
			{
				if (hasPrimaryKey)
					throw new ContentWizardException("STR_MAY_NOT_HAVE_MORE_THAN_ONE_PRIMARY_KEY");
				hasPrimaryKey = true;
				
				if (!SourceFieldTypes.GUID.equals(field.getDataType()))
					throw new ContentWizardException("STR_PRIMARY_KEY_MUST_BE_OF_TYPE_GUID");
			}
		}
	}

	private void createServiceDefinition(File serviceFolder, ICreateParams createParams) throws PortalException
	{
		Map<String, String> substituitions = new HashMap<String, String>(2, 1);
		substituitions.put("%SERVICE_ID%", createParams.getName());
		substituitions.put("%DEF-ROOT%", createParams.getDefinitionRootPath());
		
		String workflow;
		if (createParams.isContentWorkflow())
			workflow = "<workflow enabled=\"true\"/>";
		else
			workflow = "";
		substituitions.put("<!--WORKFLOW-->", workflow);

		URL template = getClass().getResource("template/servicedefinition.xml");
		File destination = new File(serviceFolder, "servicedefinition.xml");
		
		generateFile(template, substituitions, destination);
	}

	private void createDouiDefinition(File serviceFolder, ICreateParams createParams) throws PortalException
	{
		Map<String, String> substituitions = new HashMap<String, String>(2, 1);
		substituitions.put("%SERVICE_ID%", createParams.getName());
		substituitions.put("%DEF-ROOT%", createParams.getDefinitionRootPath());
		substituitions.put("<!--TABLE_NAME-->", createParams.getTable().getName());
		substituitions.put("<!--SOURCE_FIELDS-->", getSourceFieldsDefinition(createParams));
		substituitions.put("<!--METADATA-->", getSourceMetaDataDefinition(createParams));

		for (IField field: createParams.getTable().getFields())
		{
			if (field.isPrimaryKey())
			{
				substituitions.put("%PK_FIELD_ID%", field.getId());
				break;
			}
		}		

		URL template = getClass().getResource("template/douidefinition.xml");
		File destination = new File(serviceFolder, "douidefinition.xml");
		
		generateFile(template, substituitions, destination);
	}

	private void createStrings(File serviceFolder, ICreateParams createParams) throws PortalException
	{
		// create strings folder
		File stringsFolder = new File(serviceFolder, "strings");
		stringsFolder.mkdir();

		// write to the file
		try
		{
			File stringsFile = new File(stringsFolder, "strings.pt_br.xml");
			PrintWriter fileWriter = new PrintWriter(new BufferedWriter(new OutputStreamWriter(new FileOutputStream(stringsFile), "UTF-8")));
			try
			{
				fileWriter.println("<?xml version=\"1.0\" encoding=\"UTF-8\" ?>");
				fileWriter.println("<strings>");
				fileWriter.println(getStringTag("STR_SERVICE_NAME", createParams.getName()));
				fileWriter.println(getStringTag("STR_SERVICE_DESCRIPTION", createParams.getDescription()));
				fileWriter.println("</strings>");
				fileWriter.flush();
			}
			finally
			{
				fileWriter.close();
			}
		}
		catch(IOException e)
		{
			throw new ContentWizardException("STR_COULD_NOT_WRITE_STRINGS_FILE", e);
		}		
	}
	
	private String getStringTag(String id, String value)
	{
		return "<string id=\"" + id + "\"><![CDATA[" + value + "]]></string>";
	}

	private String getSourceFieldsDefinition(ICreateParams createParams)
	{
		List<IField> fields = createParams.getTable().getFields();
		StringBuilder result = new StringBuilder(100 * fields.size());
		
		// create field tags for table fields specified 
		for (IField field: fields)
		{
			result.append("<field id=\"" + field.getId() + "\" name=\"" + field.getName() + "\" dataType=\"" + field.getDataType() + "\" ");
			if (field.isRequired())
				result.append("required=\"true\" ");
			String defaultValue = field.getDefaultValue();
			if (defaultValue != null && defaultValue.length() > 0)
				result.append("defaultValue=\"" + defaultValue + "\" ");
			if (field.isPrimaryKey())
				result.append("isPrimaryKey=\"true\" display=\"false\" ");
			if (field.isPrimaryName())
				result.append("isPrimaryName=\"true\" ");
			if (field.isSearchable())
				result.append("isSearchable=\"true\" ");
			if (field.isIntroduction())
				result.append("isIntroduction=\"true\" ");
			String pattern = field.getPattern();
			if (pattern != null && pattern.length() > 0)
				result.append("pattern=\"" + pattern + "\" ");

			if (IField.FIELD_KIND_VERSION_ACTIVE.equals(field.getKind()))
				result.append("isVersionActive=\"true\" display=\"false\" ");
			else if (IField.FIELD_KIND_VERSION_NUMBER.equals(field.getKind()))
				result.append("isVersionNumber=\"true\" display=\"false\" ");
			else if (IField.FIELD_KIND_VERSION_CONTENT_ID.equals(field.getKind()))
				result.append("isVersionContentId=\"true\" display=\"false\" ");

			// Include some type specific parameters
			// Using maxLength to avoid database over and underflow in double type
			// and overflow and round in money type
			if( field.getMaxLength()>0 )
			{
				result.append("maxLength=\""+field.getMaxLength()+"\" "); 
			}
			
			// Close field
			result.append("/>\r\n");
		}
		
		return result.toString();
	}

	private String getSourceMetaDataDefinition(ICreateParams createParams)
	{
		StringBuilder result = new StringBuilder(100);
		if (createParams.isContentComments())
			result.append("<comments/>");
		if (createParams.isContentPublishToServiceInstances())
			result.append("<publishToServiceInstances/>");
		else
			result.append("<serviceInstanceIsolation/>");
		if (createParams.isContentAssociation())
			result.append("<association/>");
		if (createParams.isContentStartDate())
			result.append("<publishStartDate/>");
		if (createParams.isContentEndDate())
			result.append("<publishEndDate/>");
		if (createParams.isContentHighlight())
			result.append("<highlight/>");
		if (createParams.isContentHightlightEndDate())
			result.append("<highlightEndDate/>");
		if (createParams.isContentMultilanguage())
			result.append("<multiLanguage/>");
		if (createParams.isContentPublishToPrincipals())
			result.append("<publishToPrincipals/>");
		if (createParams.isContentVersioning())
			result.append("<versioning/>");
		if (createParams.isContentWorkflow())
			result.append("<workflow/>");
		
		if (result.length() > 0)
			return "<metaData>" + result + "</metaData>";
		else
			return "";
	}

	/**
	 * Create the table script file.
	 * @param serviceFolder the service base folder.
	 * @param createParams the parameters for creating the service.
	 * @param transaction the transaction for persistence access.
	 * @return the script generated and that has been written to the file.
	 * @throws PortalException
	 */
	private String createTableScript(File serviceFolder, ICreateParams createParams, ITransaction transaction) throws PortalException
	{
		// generate script
		IQueryAdapter queryAdapter = QueryAdapterFactory.getQueryAdapter(transaction);
		QueryCreateTable queryCreateTable = new QueryCreateTable();
		QueryTable table = new QueryTable();
		table.setTableName(createParams.getTable().getName());
		queryCreateTable.setTable(table);	
		for (IField fieldParam: createParams.getTable().getFields())
		{
			QueryField queryField = new QueryField();
			queryField.setFieldName(fieldParam.getId());
			ISourceFieldDataType sourceFieldDataType = SourceFieldTypes.getInstance(fieldParam.getDataType());
			queryField.setDataType(sourceFieldDataType.getQueryDataType());
			queryCreateTable.addField(queryField);
			if (fieldParam.isPrimaryKey())
				queryCreateTable.setPrimaryKeyFieldId(fieldParam.getId());
		}
		String createTableScript = queryAdapter.getCreateTableScript(queryCreateTable, transaction);
		
		// create database folder
		File databaseFolder = new File(serviceFolder, "database");
		databaseFolder.mkdir();
		
		// write script to tables.sql file
		try
		{
			File sqlFile = new File(databaseFolder, "tables.sql");
			FileOutputStream fileOutputStream = new FileOutputStream(sqlFile, false);
			try
			{
				OutputStreamWriter fileWriter = new OutputStreamWriter(fileOutputStream, "UTF-8");
				fileWriter.write(createTableScript);
				fileWriter.flush();
			}
			finally
			{
				fileOutputStream.close();
			}
		}
		catch(IOException e)
		{
			throw new ContentWizardException("STR_COULD_NOT_WRITE_TABLES_SQL_FILE", e);
		}		
		
		return createTableScript;
	}

	/**
	 * Generates a new file from a template applying a map of string substituitions.
	 * @param template the template URL.
	 * @param substituitions the string substituitions to apply on the template content.
	 * @param destination the destination of the new file to be created.
	 */
	private void generateFile(URL template, Map<String, String> substituitions, File destination) throws PortalException
	{
		try
		{
			if (destination.exists())
				throw new ContentWizardException("STR_FILE_ALREADY_EXISTS");

			// read content to a string
			String content;
			InputStream templateInputStream = template.openStream();
			try
			{
				InputStreamReader templateReader = new InputStreamReader(templateInputStream, "UTF-8"); 
				StringWriter stringWriter = new StringWriter(8192);
				writeContents(templateReader, stringWriter);
				content = stringWriter.getBuffer().toString();
			}
			finally
			{
				templateInputStream.close();
			}

			// do the substituitions on the string
			for (Map.Entry<String, String> entry: substituitions.entrySet())
			{
				content = TextUtil.stringReplace(content, entry.getKey(), entry.getValue());
			}

			// write content to the specified destination file
			FileOutputStream fileOutputStream = new FileOutputStream(destination, false);
			try
			{
				OutputStreamWriter fileWriter = new OutputStreamWriter(fileOutputStream, "UTF-8");
				StringReader stringReader = new StringReader(content);
				writeContents(stringReader, fileWriter);
				fileWriter.flush();
			}
			finally
			{
				fileOutputStream.close();
			}
		}
		catch(IOException e)
		{
			throw new ContentWizardException("STR_COULD_NOT_WRITE_TO_FILE", e);
		}
	}
	
	/**
	 * Read the contents from reader and write them to the writer.
	 * The contents are read until a end of stream is signaled.
	 * @param reader where the contents will be read from. 
	 * @param writer where the contents will be written to.
	 */
	private void writeContents(Reader reader, Writer writer) throws IOException
	{
		char buffer[] = new char[8192];
		int size;
		int offset = 0;
		while ((size = reader.read(buffer)) != -1)
		{
			writer.write(buffer, offset, size);
			offset += size;
		}
	}	
}